# @Time : 2025-01-14 17:29
# @Author : Fioman 
# @Phone : 13149920693
"""
分板机的pcl
"""
import threading
import time

import snap7
from snap7.util import set_bool

from common.produce_logger import debug_produce
from personal_tool.data_result import DataResult
from vision_process.calc_config import CutlineResult


class CutPlc():
    _lock = threading.Lock()
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if not hasattr(self, "_initFlag"):
            self.ip = "192.168.0.70"
            self.rack = 0  # 机架号表示 CPU 所在的物理或逻辑机架编号（S7-1200/S7-1500 默认只有一个机架，通常为 0）
            self.slot = 1  # 槽号表示 CPU 在机架中的位置（S7-1200 紧凑型设备通常为 1，S7-1500 根据配置可能为 0 或 1）。
            self.port = 102
            self.read_client = snap7.client.Client()
            self.write_client = snap7.client.Client()
            self._initFlag = True
            debug_produce(f"in CutPlc.__init__() plc初始化成功")

    def connect(self) -> DataResult:
        """
        连接PLC,返回是否连接成功
        """
        try:
            self.read_client.connect(self.ip, self.rack, self.slot, self.port)
            # 写连接（独立会话）
            self.write_client.connect(self.ip, self.rack, self.slot, self.port)
            return DataResult(True)
        except Exception as e:
            debug_produce(f"in CutPlc.connect() plc连接失败,错误信息为{e}")
            return DataResult(False, "PLC连接失败:" + str(e))

    def set_allow_plc_work(self) -> DataResult:
        """
        每个工单录完允许PLC生产信号,12.0
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 0
        return self.write_bool(dbPos, byteOffset, bitOffset, True)

    def set_stop_plc__work(self) -> DataResult:
        """
        工单完单之后,停止上料
        :return:
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 0
        return self.write_bool(dbPos, byteOffset, bitOffset, False)

    def get_allow_take_pic(self) -> DataResult:
        """
        获取是否允许拍照 2.1
        """
        dbPos = 5000
        byteOffset = 2
        bitOffset = 1
        return self.read_bool(dbPos, byteOffset, bitOffset)

    def clear_allow_get_pic_state(self):
        """
        清除允许拍照的信号 2.1
        清除的时候,将结果信号也清除掉
        :return:
        """
        dbPos = 5000
        byteOffset = 2
        bitList = [False, False]
        return self.write_bool_multiple(dbPos, byteOffset, bitList)

    def clear_total_signal(self) -> DataResult:
        """
        清除所有的信号
        :return:
        """
        dbPos = 5000
        byteOffset = 12
        bitList = [False, False, False, False, False]
        return self.write_bool_multiple(dbPos, byteOffset, bitList)

    def set_ready_to_take_pic(self) -> DataResult:
        """
        跟plc说线扫相机可以拍照 12.1,
        允许作业清除,写入允许拍照,12.2(OK信号清除),12.3重拍清除,12.4NG报警清除
        12.0清除, 12.2清除 12.3清除,12.4清除
        """
        dbPos = 5000
        byteOffset = 12
        bitList = [False, True, False, False, False]
        return self.write_bool_multiple(dbPos, byteOffset, bitList)

    def result_is_ok(self) -> DataResult:
        """
        获取结果是否OK 12.2
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 2
        return self.write_bool(dbPos, byteOffset, bitOffset, True)

    def result_is_ng(self) -> DataResult:
        """
        获取结果是否NG 12.4
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 4
        return self.write_bool(dbPos, byteOffset, bitOffset, True)

    def re_get_img(self) -> DataResult:
        """
        设置进行重拍,板回退 12.3
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 3
        return self.write_bool(dbPos, byteOffset, bitOffset, True)

    def clear_re_get_img_state(self) -> DataResult:
        """
        清除重拍的信号 12.3
        :return:
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 3
        return self.write_bool(dbPos, byteOffset, bitOffset, False)

    def is_run_state(self) -> DataResult:
        """
        设备是否是运行状态 4.4
        :return:
        """
        dbPos = 5000
        byteOffset = 4
        bitOffset = 4
        return self.read_bool(dbPos, byteOffset, bitOffset)

    def is_rs_state(self) -> DataResult:
        """
        设备是否是急停状态 6.0
        :return:
        """
        dbPos = 5000
        byteOffset = 6
        bitOffset = 0
        return self.read_bool(dbPos, byteOffset, bitOffset)

    def is_stop_state(self) -> DataResult:
        """
        是否是停止状态 4.2
        """
        dbPos = 5000
        byteOffset = 4
        bitOffset = 2
        return self.read_bool(dbPos, byteOffset, bitOffset)

    def is_zero_state(self) -> DataResult:
        """
        是否是回原点状态(也就是复位状态) 4.5
        """
        dbPos = 5000
        byteOffset = 4
        bitOffset = 5
        return self.read_bool(dbPos, byteOffset, bitOffset)

    def start_cut_plc(self) -> DataResult:
        """
        设置启动plc M0(待定)
        :return:
        """
        writePos = 720
        self.set_allow_plc_work()
        return self.write_bool(writePos, True)

    def stop_cut_plc(self) -> DataResult:
        """
        设置停止plc M1(待定)
        :return:
        """
        writePos = 818
        return self.write_bool(writePos, True)

    def reset_cut_plc(self) -> DataResult:
        """
        设置复位plc M2(待定)
        :return:
        """
        writePos = 721
        return self.write_bool(writePos, True)

    def __del__(self):
        try:
            super().__del__()
        except:
            pass

    def write_board_info(self, cutResult: CutlineResult) -> DataResult:
        start = time.time()
        if cutResult.cutLen < 300:
            return DataResult(False, f"写入的数据错误,计算的板长小于300,计算的板长为"
                                     f":{cutResult.cutLen}")
        if cutResult.cutWidth < 300:
            return DataResult(False, f"写入的数据错误,计算的板宽小雨300,计算的板宽为"
                                     f"{cutResult.cutWidth}")
        dbPos = 5000
        byteOffset = 14
        floatValues = [
            cutResult.robotX01,
            cutResult.robotY01,
            cutResult.robotR01,
            cutResult.robotX02,
            cutResult.robotY02,
            cutResult.robotR02,
            cutResult.cutLen,
            cutResult.cutWidth,
        ]
        res = self.write_float_multiple(dbPos, byteOffset, floatValues)
        if not res.state:
            return DataResult(False, res.info + f"写入耗时: {(time.time() - start):.2f} 秒")

        boolOffset = 46
        boolValues = [
            cutResult.isFirstCut,
            cutResult.isSecondCut,
            cutResult.isThirdCut,
            cutResult.isFourthCut,
        ]
        res = self.write_bool_multiple(dbPos, boolOffset, boolValues)
        if not res.state:
            res.info = res.info + f"写入耗时: {(time.time() - start):.2f} 秒"
        else:
            res.info = f"板子信息写入成功,写入耗时: {(time.time() - start):.2f} 秒"
        return res

    def set_buzzer_open_or_close(self, isOpen: bool):
        """
        设置蜂鸣器是否打开
        :param isOpen:
        :return:
        """
        dbPos = 5000
        byteOffset = 12
        bitOffset = 4
        return self.write_bool(dbPos, byteOffset, bitOffset, isOpen)

    def write_bool_multiple(self, dbPos: int, startOffset: int, boolValues: list[bool]) -> DataResult:
        """
        写入多个布尔值
        :param dbPos:
        :param startOffset:
        :param boolValues:
        :return:
        """
        try:
            with self._lock:
                byteData = self.read_client.db_read(dbPos, startOffset, 1)
            if not byteData:
                return DataResult(False, f"从DB{dbPos},offset{startOffset}读取数据失败")

            # 将字节数据转换为可修改的bytearray
            byteBuffer = bytearray(byteData)

            # 逐个修改指定位
            for bit_position, value in enumerate(boolValues):
                set_bool(byteBuffer, 0, bit_position, value)  # 参数：缓冲区，字节偏移，位偏移，值
            # 写回修改后的字节
            with self._lock:
                self.write_client.db_write(dbPos, startOffset, byteBuffer)
            return DataResult(True)
        except Exception as e:
            debug_produce(f"in CutPlc.write_bool_multiple() 写入失败,错误信息为{e}")
            return DataResult(False, "写入失败:" + str(e))

    def read_bool(self, dbNumber: int, byteOffset: int, bitOffset: int) -> DataResult:
        """
        读取DB块中的单个布尔值
        :param dbNumber: DB块号
        :param byteOffset: 字节偏移量
        :param bitOffset: 位偏移量
        :return: DataResult，data字段为布尔值
        """
        try:
            # 读取一个字节的数据
            with self._lock:
                byteData = self.read_client.db_read(dbNumber, byteOffset, 1)
            if not byteData:
                return DataResult(False, f"从DB{dbNumber},字节偏移{byteOffset}读取数据失败")

            # 将字节数据转换为bytearray
            byteBuffer = bytearray(byteData)

            # 获取指定位的值
            bitValue = (byteBuffer[0] >> bitOffset) & 1
            return DataResult(True, data=bool(bitValue))
        except Exception as e:
            debug_produce(f"in CutPlc.readDbBool() 读取失败,错误信息为{e}")
            return DataResult(False, f"读取DB{dbNumber}.{byteOffset}.{bitOffset}失败: {str(e)}")

    def write_bool(self, dbNumber: int, byteOffset: int, bitOffset: int, value: bool) -> DataResult:
        """
        写入单个布尔值到指定的DB块位置
        :param dbNumber: DB块号，例如5000
        :param byteOffset: 字节偏移量，例如12
        :param bitOffset: 位偏移量，例如0表示12.0
        :param value: 要写入的布尔值
        :return: DataResult
        """
        try:
            # 读取当前字节的值
            with self._lock:  # 添加线程锁
                byteData = self.read_client.db_read(dbNumber, byteOffset, 1)
            if not byteData:
                return DataResult(False, f"从DB{dbNumber},字节偏移{byteOffset}读取数据失败")

            # 将字节数据转换为可修改的bytearray
            byteBuffer = bytearray(byteData)

            # 修改指定位的值
            set_bool(byteBuffer, 0, bitOffset, value)

            # 写回修改后的字节
            with self._lock:
                self.write_client.db_write(dbNumber, byteOffset, byteBuffer)
            return DataResult(True)
        except Exception as e:
            debug_produce(f"in CutPlc.writeBool() 写入失败,错误信息为{e}")
            return DataResult(False, f"写入DB{dbNumber}.{byteOffset}.{bitOffset}失败: {str(e)}")

    def write_float_multiple(self, dbNumber: int, startByteOffset: int, values: list[float]) -> DataResult:
        """
        写入多个32位浮点数到指定的DB块位置
        :param dbNumber: DB块号
        :param startByteOffset: 起始字节偏移量
        :param values: 要写入的浮点数列表
        :return: DataResult
        """
        try:
            import struct
            # 计算需要的总字节数（每个浮点数4字节）
            totalBytes = len(values) * 4

            # 创建一个足够大的bytearray
            byteBuffer = bytearray(totalBytes)

            # 将每个浮点数转换为4字节并写入缓冲区
            for i, value in enumerate(values):
                # 将浮点数格式化为两位小数
                formatted_value = round(value, 2)
                # 转换为IEEE 754格式的字节
                float_bytes = struct.pack('>f', formatted_value)
                # 写入到缓冲区
                byteBuffer[i * 4:(i + 1) * 4] = float_bytes

            # 写入到PLC
            with self._lock:
                self.write_client.db_write(dbNumber, startByteOffset, byteBuffer)
            return DataResult(True)
        except Exception as e:
            debug_produce(f"in CutPlc.writeFloats() 写入失败,错误信息为{e}")
            return DataResult(False, f"写入浮点数到DB{dbNumber}失败: {str(e)}")

    def write_float(self, dbNumber: int, byteOffset: int, value: float) -> DataResult:
        """
        写入单个32位浮点数到指定的DB块位置
        :param dbNumber: DB块号，例如5000
        :param byteOffset: 字节偏移量，例如14
        :param value: 要写入的浮点数
        :return: DataResult
        """
        try:
            import struct
            # 将浮点数格式化为两位小数
            formatted_value = round(value, 2)
            # 转换为IEEE 754格式的字节
            float_bytes = struct.pack('>f', formatted_value)

            # 写入到PLC
            with self._lock:
                self.write_client.db_write(dbNumber, byteOffset, float_bytes)
            return DataResult(True)
        except Exception as e:
            debug_produce(f"in CutPlc.writeFloat() 写入失败,错误信息为{e}")
            return DataResult(False, f"写入浮点数到DB{dbNumber}.{byteOffset}失败: {str(e)}")


if __name__ == '__main__':
    cutPlc = CutPlc()
    result = cutPlc.connect()
    if result.state:
        # print("连接成功")
        # cutLineResult = CutlineResult()
        # cutLineResult.robotX01 = 111.11
        # cutLineResult.robotY01 = 222.22
        # cutLineResult.robotR01 = 3.33
        # cutLineResult.robotX02 = 444.44
        # cutLineResult.robotY02 = 555.55
        # cutLineResult.robotR02 = 6.66
        # cutLineResult.cutLen = 777.77
        # cutLineResult.cutWidth = 888.88
        # cutLineResult.isFirstCut = True
        # cutLineResult.isSecondCut = False
        # cutLineResult.isThirdCut = True
        # cutLineResult.isFourthCut = False
        # cutLineResult.pack_info()
        # print(cutLineResult.info)
        # res = cutPlc.write_board_info(cutLineResult)
        # print(res.info)
        result = cutPlc.set_ready_to_take_pic()
        if not result.state:
            print(result.info)
    else:
        print("连接失败")
